package models.base;

import ext.jpa.UUIDModel;
import helper.BeanHelper;
import helper.GlobalConfig;
import jodd.bean.BeanCopy;
import models.constants.DeletedStatus;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.annotations.Index;
import play.Logger;
import play.data.validation.Required;
import play.modules.paginate.JPAExtPaginator;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 公司部门信息.
 */
@Entity
@Table(name = "depts", schema = GlobalConfig.MIGAO_DB_SCHEMA)
@org.hibernate.annotations.Table(
        appliesTo = "depts",
        indexes = {
                @Index(name = "idx_depts_createdat", columnNames = "created_at"),
                @Index(name = "idx_depts_root_corp", columnNames = "root_corp_id")
        }
)
public class Dept extends UUIDModel {

    private static final long serialVersionUID = 3766340472716143470L;

    /**
     * 所属公司.
     */
    @ManyToOne
    @JoinColumn(name = "corp_id")
    public Corp corp;

    /**
     * 部门名称.
     */
    @Required
    @Column(name = "name", length = 50)
    public String name;

    /**
     * 部门短名称.
     */
    @Column(name = "short_name", length = 50)
    public String shortName;

    /**
     * 部门description.
     */
    @Column(name = "description", length = 250)
    public String description;

    @ManyToOne
    @JoinColumn(name = "parent_id", nullable = true)
    public Dept parent;

    /**
     * 显示顺序.
     */
    @Column(name = "show_order", columnDefinition = "integer")
    public Integer showOrder;

    /**
     * 微信企业号维护的部门ID.
     *
     * 如果是根部门，wxDeptId是1
     */
    @Column(name = "wx_dept_id", columnDefinition = "integer")
    public Integer wxDeptId;

    /**
     * 上游系统的部门ID. 如果是LDAP, 使用distinguishedName
     */
    @Column(name = "upstream_id", length = 512)
    public String upstreamId;

    /**
     * 集团总公司ID.
     * 如果为空, 表示不需要处理这方面的逻辑.
     */
    @Column(name = "root_corp_id", columnDefinition = "char(22)")
    public String rootCorpId;

    /**
     * 禁止与上游同步，只有在upstreamId不为空时才有用.
     */
    @Column(name = "disable_sync_upstream")
    public Boolean disableSyncUpstream;

    /**
     * 逻辑删除,0:未删除，1:已删除
     */
    @Enumerated(EnumType.ORDINAL)
    @Column(name = "deleted")
    public DeletedStatus deleted = DeletedStatus.UN_DELETED;

    /**
     * 创建时间.
     */
    @Column(name = "created_at")
    public Date createdAt;

    /**
     * 更新时间.
     */
    @Column(name = "updated_at")
    public Date updatedAt;

    @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
    @JoinTable(name = "depts_users",
            inverseJoinColumns = @JoinColumn(name = "user_id"),
            joinColumns = @JoinColumn(name = "dept_id"))
    public Set<AdminUser> users;

    @Transient
    private Integer validUserCount;

//    @Transient
//    public Integer getValidUserCount() {
//        if (validUserCount == null) {
//            validUserCount = AdminUser.countValidUserByDept(this);
//        }
//        return validUserCount;
//    }

    /**
     * 建立部门.
     */
    public static Dept create(Corp corp, String name) {
        return create(corp, name, null);
    }

    public static Dept create(Corp corp, String name, String parentId) {
        Dept dept = new Dept();
        dept.name = name;
        dept.corp = corp;
        dept.rootCorpId = corp.rootCorpId;
        dept.createdAt = new Date();
        dept.updatedAt = new Date();
        dept.deleted = DeletedStatus.UN_DELETED;
        if (StringUtils.isNotBlank(parentId)) {
            Dept parent = Dept.findByCorpAndId(corp, parentId);
            if (parent != null) {
                dept.parent = parent;
            }
        }
        List<Dept> allDepts = findByCorp(corp);
        dept.showOrder = allDepts.size() + 1;
        dept.create();
        return dept;
    }

    public static Dept update(String id, Dept newDept) {
        Dept oldDept = Dept.findById(id);
        BeanCopy.beans(newDept, oldDept).ignoreNulls(true).copy();
        oldDept.disableSyncUpstream = newDept.disableSyncUpstream();
        oldDept.updatedAt = new Date();
        oldDept.save();
        return oldDept;
    }

    public static Dept findByWxDeptId(Corp corp, Integer wxDeptId) {
        return Dept.find("corp=?1 and wxDeptId=?2 and deleted=?3",
                corp, wxDeptId, DeletedStatus.UN_DELETED).first();
    }

    public static Dept findByDeptId(Corp corp, String deptId) {
        return findByCorpAndId(corp, deptId);
    }

    public static Dept findByDeptName(Corp corp, String name) {
        return Dept.find("corp=?1 and name=?2 and deleted=?3",
                corp, name, DeletedStatus.UN_DELETED).first();
    }

    public static List<Dept> findByCorp(Corp corp) {
        return Dept.find("corp=?1 and deleted=?2 order by showOrder",
                corp, DeletedStatus.UN_DELETED).fetch();
    }

    public static List<Dept> findByRelationCorp(Corp corp) {
        return Dept.find("(corp=?1 or rootCorpId=?2) and deleted=?3 order by showOrder",
                corp, corp.rootCorpId(), DeletedStatus.UN_DELETED).fetch();
    }

    public static List<Dept> findByRelationCorpOrderByCreatedAt(Corp corp) {
        return Dept.find("(corp=?1 or rootCorpId=?2) and deleted=?3 order by createdAt desc",
                corp, corp.rootCorpId(), DeletedStatus.UN_DELETED).fetch();
    }


    public static List<Dept> findByRelationCorp(Corp corp, String search) {
        return Dept.find("(corp=?1 or rootCorpId=?2) and name like ?3 and deleted=?4 order by showOrder",
                corp, corp.rootCorpId(), "%" + search + "%", DeletedStatus.UN_DELETED).fetch();
    }

    public static Dept findByUpstreamId(Corp corp,  String upstreamId) {
        return Dept.find("(corp=?1 or rootCorpId=?2) and upstreamId=?3 and deleted=?4 and corp.deleted=?5 order by createdAt desc",
                corp, corp.rootCorpId(), upstreamId, DeletedStatus.UN_DELETED, DeletedStatus.UN_DELETED).first();
    }

    public static List<Dept> findByCorpAndIds(Corp corp, Collection<String> ids) {
        if (ids == null || ids.size() == 0) {
            return Collections.emptyList();
        }
        List<String> deptIdList = new ArrayList<>();
        deptIdList.addAll(ids);

        return Dept.find("(corp=?1 or rootCorpId=?2) and id in ?3 and deleted=?4 order by showOrder",
                corp, corp.rootCorpId(), deptIdList, DeletedStatus.UN_DELETED).fetch();
    }

    public static List<Dept> findByCorpWithDeleted(Corp corp) {
        return Dept.find("corp=?1 order by showOrder", corp).fetch();
    }

    /**
     * 找出当前公司及子公司的记录
     */
    public static List<Dept> findByCorpOrChildCorp(Corp corp) {
        return Dept.find("(corp=?1 or rootCorpId=?2) and deleted=?3 order by showOrder",
                corp, corp.rootCorpId(), DeletedStatus.UN_DELETED).fetch();
    }

    public static Dept findByCorpAndId(Corp corp, String id) {
        return Dept.find("(corp=?1 or rootCorpId=?2) and id=?3", corp, corp.rootCorpId(), id).first();
    }

    public static Long countForCorp(Corp corp) {
        if (corp == null) {
            return 0L;
        }
        return Dept.count("corp=?1 and deleted=?2", corp, DeletedStatus.UN_DELETED);
    }

    @Override
    public Dept delete() {
        if (this.deleted == DeletedStatus.UN_DELETED) {
            this.deleted = DeletedStatus.DELETED;
            this.updatedAt = new Date();
            this.save();
        }
        return this;
    }

    /**
     * 逻辑删除.
     */
    public static void delete(Corp corp, String id) {
        Dept dept = Dept.findByCorpAndId(corp, id);
        if (dept != null) {
            dept.deleted = DeletedStatus.DELETED;
            dept.updatedAt = new Date();
            dept.save();
        }
    }

    /**
     * 改变顺序.
     *
     * @param corp       指定公司
     * @param id         目标ID
     * @param showOrder 目标序号
     */
    public static void changeSortOrder(Corp corp, String id, Integer showOrder) {
        Dept currentDept = Dept.findByCorpAndId(corp, id);

        if (currentDept == null) {
            return;
        }

        List<Dept> depts = null;

        if (currentDept.parent == null) {
            depts = find("corp=?1 and deleted=?2 and id <> ?3 and parent is null order by showOrder",
                    corp, DeletedStatus.UN_DELETED, id).fetch();
        } else {
            // 只修改同级别的
            depts = find("corp=?1 and deleted=?2 and id <> ?3 and parent = ?4 order by showOrder",
                    corp, DeletedStatus.UN_DELETED, id, currentDept.parent).fetch();
        }
        for (int i = 1; i < depts.size() + 1; i++) {
            Dept dept = depts.get(i - 1);
            dept.showOrder = (i >= showOrder) ? (i + 1) : i;
            dept.save();
        }
        Dept dept = Dept.findById(id);
        dept.showOrder = showOrder;
        dept.save();
    }

    /**
     * 禁止同步upstream.
     */
    @Transient
    public Boolean disableSyncUpstream() {
        return this.disableSyncUpstream != null && this.disableSyncUpstream;
    }

    /**
     * 分页查询.
     */
    public static JPAExtPaginator<Dept> findByCondition(Map<String, Object> conditionMap, String orderByExpress, int pageNumber, int pageSize) {
        StringBuilder xsqlBuilder = new StringBuilder("t.deleted=models.constants.DeletedStatus.UN_DELETED ")
                .append("/~ and t.id = {id} ~/")
                .append("/~ and t.corp.id = {corpId} ~/")
                .append("/~ and t.wxDeptId = {wxDeptId} ~/")
                .append("/~ and t.name = {name} ~/")
                .append("/~ and t.parent.id = {parent} ~/")
                .append("/~ and t.showOrder = {showOrder} ~/");

        util.xsql.XsqlBuilder.XsqlFilterResult result = new util.xsql.XsqlBuilder().generateHql(xsqlBuilder.toString(), conditionMap);

        JPAExtPaginator<Dept> deptPage = new JPAExtPaginator<>("Dept t", "t", Dept.class,
                result.getXsql(), conditionMap).orderBy(orderByExpress);
        Logger.info("SQL :" + result.getXsql());
        deptPage.setPageNumber(pageNumber);
        deptPage.setPageSize(pageSize);
        deptPage.setBoundaryControlsEnabled(false);
        return deptPage;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) return false;

        Dept dept = (Dept) o;

        return new EqualsBuilder()
                .appendSuper(super.equals(o))
                .append(corp, dept.corp)
                .append(name, dept.name)
                .append(showOrder, dept.showOrder)
                .append(deleted, dept.deleted)
                .append(createdAt, dept.createdAt)
                .append(updatedAt, dept.updatedAt)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37)
                .appendSuper(super.hashCode())
                .append(corp)
                .append(name)
                .append(showOrder)
                .append(deleted)
                .append(createdAt)
                .append(updatedAt)
                .toHashCode();
    }

    @Override
    public String toString() {
        return "Dept{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", parent=" + BeanHelper.getModelId(parent) +
                ", showOrder=" + showOrder +
                ", upstreamId=" + upstreamId +
                ", deleted=" + deleted +
                '}';
    }
}
